From smart DB tricks to frontend lazy-loading — these are the real-world optimizations that made our stack fly.
When your site suddenly feels sluggish or your API response times creep up without warning, it’s easy to assume your infrastructure is to blame.
But more often than not, the bottlenecks lie in your code — how you fetch data, how you load assets, and how your app communicates across the stack.
Here are 10 field-tested performance tips we’ve applied to real-world projects using Next.js, React, and Node.js (Express).
Functions like COUNT,SUM,AVG
on large tables can crush your database if called frequently, especially on high-traffic pages.
Instead:
Pre-calculate via scheduled cron jobs or worker queues
Cache the results in Redis or a “summary” DB table
Display cached results by default, with a “refresh” button if needed
Only run real-time calculations on detail views, not overview pages
Pro tip: If your pages are slowly degrading over time, check your aggregate queries first.
Next.js's next/image component performs real-time resizing on the server. While it’s powerful, it introduces overhead
— especially for simple static images or serverless environments.
Use it wisely:
Scenario | Recommended Strategy |
---|---|
CDN-hosted static images | Use raw |
Internal uploads or resizing needed | Use with loader |
FaaS-based SSR or remote URLs | Avoid |
Many developers assumeuseEffect() waits until a component is visible to run
— but it doesn’t. It fires as soon as the component is mounted.
For smarter network usage, use react-intersection-observer
to trigger data fetch only when the user scrolls into view.
const { ref, inView } = useInView({ rootMargin: '400px', triggerOnce: true });
useEffect(() => { if (inView) fetchData(); }, [inView]);
Don’t load 100 items at once. Instead, use infinite scroll to improve perceived performance and user engagement.
What to handle:
Prevent duplicate fetches with useRef or throttling
Free up memory for unmounted elements if possible
Keep UX consistent by showing loading indicators
In Next.js, if a component doesn’t need SSR, you can dynamically import it to reduce your initial JavaScript bundle size.
const MapModal = dynamic(() => import('../MapModal'), { ssr: false });
Use this for modals, tooltips, or sections far down the page — anything not needed at first paint.
Every middleware runs on every route unless scoped. For API performance, keep global middleware to a minimum.
Better approach:
app.use('/api/private', authMiddleware); // scoped app.get('/robots.txt', (req, res) => res.send('User-agent: *\nDisallow:'));
Heavy .png
and .jpg
uploads slow down pages — especially on mobile.
Automate conversion and compression using
sharp(imageBuffer) .resize({ width: 800 }) .toFormat('webp') .toBuffer()
Then store it in S3 and serve via CloudFront for CDN acceleration.
HTTP/2 brings faster multiplexing and header compression — but your origin (like S3) might not support it natively.
Solution:
Use CloudFront in front of S3
Rewire URLs on the fly:
function rewriteToCDN(url) { return url.replace('https://bucket.s3.region.amazonaws.com', 'https://cdn.example.com'); }
Enable aggressive caching headers for static assets
Too many open connections = memory bloat. Too few = slow queries.
PostgreSQL example using
const pool = new Pool({ max: 10, idleTimeoutMillis: 30000, });
Always tune this based on traffic and load testing.
Calling await in series increases I/O wait time.
Bad:
await saveUser(); await sendEmail();
Good:
await Promise.all([saveUser(), sendEmail()]);
use map + Promise.all() instead of forEach
Performance isn’t a one-time tweak — it’s a discipline.
You don’t need to implement all 10 tips at once. Start by identifying your bottlenecks, then layer in the relevant optimizations.
Every millisecond you shave off adds up to better UX, lower infra costs, and a happier dev team.